<?php

namespace common\models\CommonModel;

use common\components\DataHelper;
use yii\base\DynamicModel;
use yii\db\ActiveRecord;
use yii\db\Connection;
use yii\db\Exception;
use yii\helpers\ArrayHelper;

/**
 * 公共model
 * Class CommonModel
 * @package common\models\CommonModel
 */
abstract class CommonModel extends ActiveRecord
{
    protected static $_ERRORS = '';
    public static $errCode = 1;
    /**
     * 控制软删除字段
     * @var string
     */
    protected $delField = 'is_del';

    /**
     * 作用于软删除
     * 已被删除标示
     */
    const DELETE_YES = 1;

    /**
     * 作用于软删除
     * 未被删除标示
     */
    const DELETE_NO = 0;

    /**
     * 更新操作
     */
    const OPERATE_UPDATE = 'update';

    /**
     * 插入操作
     */
    const OPERATE_INSERT = 'insert';

    public static $EXTENDS_JSON_NAME = "extends_json";

    const STATUS_YES = 1;
    const STATUS_NO = 0;

    const IS_YES = 1;
    const IS_NO = 0;

    public static function getStatusMap($key = "ALL", $default = false)
    {
        $ret = [
            self::STATUS_YES => "正常",
            self::STATUS_NO => "禁用"
        ];

        if ( $key === "ALL" ) {
            return $ret;
        }
        return ArrayHelper::getValue($ret, $key, $default);
    }

    /**
     * 需要获取的字段
     * @var array
     */
    public $fields = [];

    /**
     * 是否过滤软删除
     * @var bool
     */
    public $filterSoftDel = true;

    /**
     * 是否自动获取事务处理
     * 针对updateCommon insertCommon updateByPkCommon
     * @var bool
     */
    public $isAutoTransaction = true;

    private static $isForUpdate = [];

    public static function tableNameFull()
    {
        preg_match("/dbname=([^;]+)/i", static::getDb()->dsn, $matches);
        return $matches[1] . "." .parent::tableName(); // TODO: Change the autogenerated stub
    }

    public static function getUniqueTableNameMd5()
    {
        return md5(self::getDb()->dsn . self::tableName());
    }

    public function getIsForUpdate($pk = 0)
    {
        if ( !$pk ) {
            $pk = $this->getPrimaryKey();
        }
        $tablesName = self::getUniqueTableNameMd5();
        if ( isset(self::$isForUpdate[$tablesName][$pk]) && self::$isForUpdate[$tablesName][$pk] ) {
            return true;
        }
        return false;
    }

    public  function setIsForUpdate($bool, $pk = 0)
    {
        if ( !$pk ) {
            $pk = $this->getPrimaryKey();
        }
        $tablesName = self::getUniqueTableNameMd5();
        if ( $bool ) {
            self::$isForUpdate[$tablesName][$pk] = true;
        } else {
            if ( !isset(self::$isForUpdate[$tablesName]) ) {
                return true;
            }
            if ( isset(self::$isForUpdate[$tablesName][$pk]) ) {
                unset(self::$isForUpdate[$tablesName][$pk]);
            }
            if ( !self::$isForUpdate[$tablesName] ) {
                unset(self::$isForUpdate[$tablesName]);
            }
        }
        return true;
    }

    public static function getIsForUpdateAttribute()
    {
        return self::$isForUpdate;
    }

    /*public function behaviors()
    {
        return [
            '\yiidreamteam\behaviors\LockableActiveRecord',
        ];
    }*/

    /**
     * 这里的条件一定要带上主键或者唯一索引
     * @param array $condition
     * @return array|null|static
     */
    public static function findOneLockForUpdate($condition  = [])
    {
        return self::find()->where($condition)->lockForUpdateOne();
    }

    /**
     * 优化原本的forUpdate
     * 保证加锁后的数据是最新的
     */
    public function lockForUpdate()
    {
        if ( $this->getIsNewRecord() ) {
            return ;
        }
        if (static::getDb()->getTransaction() === null) {
            throw new \BadMethodCallException('Active transaction is required');
        }
        //如果已经被锁定 则不执行任何操作
        if ( $this->getIsForUpdate() ) {
            return ;
        }
        $pk = $this->getPrimaryKey(true);
        $sql = static::find()->andWhere($pk)->createCommand()->getRawSql() . ' FOR UPDATE';
        $ret = static::getDb()->createCommand($sql)->queryOne();
        foreach ( $ret as $key => $item ) {
            isset($this->$key) && $this->$key = $item;
        }
        $this->setIsNewRecord(false);
        //添加事件处理
        $this->getDb()->on(Connection::EVENT_COMMIT_TRANSACTION, function($e){
            $e->data->setIsForUpdate(false);
            $this->getDb()->off(Connection::EVENT_COMMIT_TRANSACTION);
            $this->getDb()->off(Connection::EVENT_ROLLBACK_TRANSACTION);
        }, $this);
        $this->getDb()->on(Connection::EVENT_ROLLBACK_TRANSACTION, function($e){
            $e->data->setIsForUpdate(false);
            $this->getDb()->off(Connection::EVENT_COMMIT_TRANSACTION);
            $this->getDb()->off(Connection::EVENT_ROLLBACK_TRANSACTION);
        }, $this);
        $this->setIsForUpdate(true);
        return ;
    }

    public function setExtendJson($key, $value, $query = "")
    {
        if ( !$query ) {
            $query = $this;
        }
        $extendJsonName = static::$EXTENDS_JSON_NAME;
        $extendJson = $query->$extendJsonName;
        if ( !$extendJson ) {
            $extendJson = [];
        }
        if ( !is_array($extendJson) ) {
            $extendJson = json_decode($extendJson, true);
        }
        if ( $value === null ) {
            unset($extendJson[$key]);
        } else {
            $extendJson[$key] = $value;
        }
        $query->$extendJsonName = json_encode($extendJson);
        return ;
    }

    /**
     * 获取扩展参数
     * @param $key
     * @param string $query
     * @param $default
     * @return mixed
     */
    public function getExtendJson($key, $query = "", $default = false)
    {
        if ( !$query ) {
            $query = $this;
        }
        $extendJsonName = static::$EXTENDS_JSON_NAME;
        if ( !is_array($query->$extendJsonName) ) {
            $extendsJson = json_decode($query->$extendJsonName, true);
        } else {
            $extendsJson = $query->$extendJsonName;
        }
        return ArrayHelper::getValue($extendsJson, $key, $default);
    }

    /**
     * 设置关联表数据
     * @param $mainName
     * @param $mainVal
     * @param $linkName
     * @param array $data
     * @return array|bool
     */
    public function setLinkRows($mainName, $mainVal, $linkName, array $data)
    {
        $pk = self::primaryKey()[0];

        $ret = self::find()->where([
            $mainName => $mainVal
        ])->indexBy($pk)->asArray()->all();

        $insertIds = [];
        //如果关联表中没有关联数据则直接insert
        if ( !$ret ) {
            foreach ( $data as $item ) {
                if ( is_array($item) ) {
                    $this->attributes = $item;
                } else {
                    $this->$linkName = $item;
                }
                $this->$mainName = $mainVal;
                $this->id = null;
                $this->isNewRecord = true;
                $this->insert(0);
                $insertIds[] = $this->$pk;
            }
            return [
                "deletes" => [],
                "insert" => $insertIds
            ];
        }

        $transaction = self::getDb()->beginTransaction();

        //这里来拿出需要被删除的数据和不需要处理的数据
        $first = reset($data);
        if ( is_array($first) ) {
            $tmpData = array_column($data, $linkName, $pk);
        } else {
            $tmpData = $data;
        }
        foreach ( $ret as $retItem ) {
            if ( !in_array($retItem[$linkName], $tmpData) ) {
                $deletes[] = $retItem[$pk];
            } else {
                $key = array_search($retItem[$linkName], $tmpData);
                unset($data[$key]);
            }
        }

        if ( isset($deletes) && $deletes ) {
            $delRet = self::deleteAll([
                $pk => $deletes,
            ]);
            if ( !$delRet ) {
                $transaction->rollBack();
                $this->addError($pk, "系统繁忙");
                return false;
            }
        }
        foreach ( $data as $item ) {
            if ( is_array($item) ) {
                $this->attributes = $item;
            } else {
                $this->$linkName = $item;
            }
            $this->$mainName = $mainVal;
            $this->$pk = null;
            $this->isNewRecord = true;
            $this->insert(0);
            $insertIds[] = $this->$pk;

        }

        $transaction->commit();

        return [
            "deletes" => isset($deletes) ? $deletes : [],
            "insert" => $insertIds
        ];
    }

    /**
     * 独立的数组验证
     * @param array $attributes
     * @param $rules
     * @param null $retainAttribute
     * @return bool|mixed
     */
    public function arrayValidator(array &$attributes, $rules, $retainAttribute = null)
    {
        if ( $rules instanceof \Closure ) {
            return $retainAttribute ? $rules($attributes, $retainAttribute, $this) : $rules($attributes, $this);
        }

        //这里过滤多余的json数据
        if ( $retainAttribute ) {
            $attributes = DataHelper::filterAttribute($retainAttribute, $attributes, true);
        }

        //这里来验证数据
        $dynamicModel = DynamicModel::validateData($attributes, $rules);

        if ( $dynamicModel->hasErrors() ) {
            $this->addErrors($dynamicModel->getErrors());
            return false;
        }

        $attributes = $dynamicModel->attributes;

        return true;
    }

    /**
     * 作用域find
     * @return \common\models\CommonModel\CommonQuery
     */
    public static function find()
    {
    	return new CommonQuery(get_called_class());
    }

    /**
     * 设置字段
     * @param array $arrFields
     * @param bool $isExclude
     */
    public function setFields(array $arrFields, $isExclude=false)
    {
        if ( $isExclude ) {
            $this->fields = array_diff(array_keys(self::getTableSchema()->columns), $arrFields);
        } else {
            $this->fields = $arrFields;
        }

        return ;
    }

    /**
     * 获取对象数据列表
     * @param array $condition
     * @param bool $isObj
     * @return array|CommonQuery|ActiveRecord[]
     */
    public function getListAll($condition=[], $isObj=false)
    {
    	empty($this->fields) && $this->fields = [
    	 '*',
    	];
    	
    	$query = $this->find()->select($this->fields)->where($condition);
        $this->filterSoftDel && $query->notDel($this, $this->delField, static::DELETE_NO);

        if ( $isObj ) return $query;
    	
    	return $query->asArray()->all();
    }
    
    /**
     * 通过主键获取一条数据
     * @param $pk
     * @param array $arrCondition
     * @param bool $isObj
     * @return static ActiveRecord|array
     */
    public function getRowByPk($pk, array $arrCondition=[], $isObj=false)
    {
        $query = $this->find()->select($this->fields)->
            where(sprintf('%s=:pk', self::primaryKey()[0]), [':pk'=>$pk])->
            andWhere($arrCondition);

        $this->filterSoftDel && $query->notDel($this, $this->delField, static::DELETE_NO);
        $query->limit(1);

        if ( $isObj ) return $query->one();

        return $query->asArray()->one();
    }

    /**
     * 根据自定义条件获取一条数据
     * @param array $arrCondition
     * @param bool $isObj
     * @return static ActiveRecord|array
     */
    public function getRowByCondition(array $arrCondition, $isObj=false)
    {
    	$query = $this->find()->select($this->fields)->
	    	where($arrCondition);

    	$this->filterSoftDel && $query->notDel($this, $this->delField, static::DELETE_NO);
    	$query->limit(1);
    	
    	if ( $isObj ) return $query->one();
    	
    	return $query->asArray()->one();
    }

    /**
     * 通过主键硬删除一条数据
     * @param $pk
     * @param array $arrCondition 附加条件
     * @return bool|false|int
     * @throws \Exception
     */
    public function delByPkForHard($pk, array $arrCondition=[])
    {
        $pkName = self::primaryKey()[0];

        //条件合并处理
        if ( isset($arrCondition[0]) && is_string($arrCondition[0]) ) {
            $arrCondition = !is_array($pk) ? array_merge($arrCondition,
                [['and', ['=', self::primaryKey()[0], $pk]]]
            ) : array_merge($arrCondition,
                [['and', ['in', self::primaryKey()[0], $pk]]]
            );
        } else {
            $arrCondition = array_merge($arrCondition, [$pkName=>$pk]);
        }

        if ( !($result = $this->deleteAll($arrCondition)) ) {
            $this->addError($pkName, '删除失败！');
        }

        return $result;
    }

    /**
     * 通过主键硬删除一条数据
     * @param array $arrCondition 附加条件
     * @return bool|false|int
     * @throws \Exception
     */
    public function delByConditionForHard(array $arrCondition=[])
    {
        $pkName = self::primaryKey()[0];

        if ( !($result = $this->deleteAll($arrCondition)) ) {
            $this->addError($pkName, '删除失败！');
        }

        return $result;
    }

    /**
     * 通过主键软删除一条数据
     * @param $pk
     * @param array $arrCondition 附加条件
     * @return bool|false|int
     * @throws \Exception
     */
    public function delByPkForSoft($pk, array $arrCondition=[])
    {
    	$pkName = self::primaryKey()[0];

        //条件合并处理
        if ( isset($arrCondition[0]) && is_string($arrCondition[0]) ) {
            $arrCondition = !is_array($pk) ? array_merge($arrCondition,
                [['and', ['=', self::primaryKey()[0], $pk]]]
            ) : array_merge($arrCondition,
                [['and', ['in', self::primaryKey()[0], $pk]]]
            );
        } else {
            $arrCondition = array_merge($arrCondition, [$pkName=>$pk]);
        }

    	if ( !($result = $this->updateAll([$this->delField=>static::DELETE_YES], $arrCondition)) ) {
            $this->addError($pkName, '删除失败！');
        }

    	return $result;
    }

    /**
     * 删除软一条数据
     * @param array $arrCondition
     * @return bool
     */
    public function delByConditionForSoft(array $arrCondition=[])
    {
        $pkName = self::primaryKey()[0];

        if ( !($result = $this->updateAll([$this->delField=>static::DELETE_YES], $arrCondition)) ) {
            $this->addError($pkName, '删除失败！');
        }

        return $result;
    }

    /**
     * 通过主键删除一条数据 自动判断是软删除还是硬删除
     * @param $pk
     * @param array $arrCondition
     * @return bool|false|int
     */
    public function delByPk($pk, array $arrCondition=[])
    {
    	//如果当前表存在is_del字段 则使用软删除
    	if ( in_array($this->delField, $this->attributes()) ) {
    		return $this->delByPkForSoft($pk, $arrCondition);
    	}
    	
    	return $this->delByPkForHard($pk, $arrCondition);
    }

    /**
     * 自动判断是软删除还是硬删除
     * @param array $arrCondition
     * @return bool|false|int
     */
    public function delByCondition(array $arrCondition=[])
    {
        //如果当前表存在is_del字段 则使用软删除
        if ( in_array($this->delField, $this->attributes()) ) {
            return $this->delByConditionForSoft($arrCondition);
        }

        return $this->delByConditionForHard($arrCondition);
    }

    
    /**
	 * 更新时的数据合并 
	 * 这部分数据表示不参与验证 
	 * 在验证之后与将要更新的数据合并一起更新
     */
    public function defaultAsUpdate()
    {
    	return true;
    }

    /**
     * 更新指定query数据
     * @param $pk
     * @param array $arrRequest
     * @param string $scenarios
     * @param array $arrCondition
     * @param $query
     * @return bool|int|null
     * @throws \Exception
     */
    public function updateByPkCommon($pk, array $arrRequest, $scenarios='default', array $arrCondition=[], &$query = null)
    {
    	$query = $this->getRowByPk($pk, $arrCondition, true);
    	
    	if ( empty($query) ) {
            $this->addError(self::primaryKey()[0], '未找到指定对象！');
            return false;
        }
    	
    	$query->scenario = $scenarios;
    	$query->attributes = $arrRequest;
    	
    	if ( !$query->validate() ) {
    		$this->addErrors($query->errors);
    		return false;
    	}
    	
    	//条件合并处理
        if ( isset($arrCondition[0]) && is_string($arrCondition[0]) ) {
            $arrCondition = !is_array($pk) ? array_merge($arrCondition,
                [['and', ['=', self::primaryKey()[0], $pk]]]
            ) : array_merge($arrCondition,
                [['and', ['in', self::primaryKey()[0], $pk]]]
            );
        } else {
            $arrCondition[self::primaryKey()[0]] = $pk;
        }

    	$func = sprintf('%sAsUpdate', $scenarios);

    	//根据对应场景选择触发执行方法
    	if ( method_exists($this, $func) ) {
            $return = $this->$func($query);
    	} else {
    	    $func = sprintf('%sAsDefault', $scenarios);
    	    if ( method_exists($this, $func) ) {
                $return = $this->$func(self::OPERATE_UPDATE, $query);
            } else {
    	        $return = $this->defaultAsUpdate();
            }
    	}

        //如果事件函数返回值为false则 终止余下操作
        if ( $return === false ) {
    	    return false;
        }

    	if ( !is_bool($return) ) {
    		throw new \Exception($func.'返回值必须是一个bool');
    	}
    	
    	$return = $this->updateAll($query->attributes, $arrCondition);

        #获取活动事务
        $transaction = self::getDb()->getTransaction();

        //如果有活动事务
        if ( $transaction && $this->isAutoTransaction ) {
            if ( $return === false ) {
                $transaction->rollBack();
                $this->addError(self::primaryKey()[0], '数据异常');
            } else {
                $transaction->commit();
            }
        }

        return $return;
    }

    /**
     * 更新指定条件数据
     * @param $query
     * @param array $arrRequest
     * @param array $arrCondition
     * @param string $scenarios
     * @return bool|int|null
     * @throws \Exception
     */
    public function updateCommon(array $arrRequest, array $arrCondition=[], $scenarios='default', &$query = null)
    {
        $query = $this->getRowByCondition($arrCondition, true);

        if ( empty($query) ) {
            $this->addError(self::primaryKey()[0], '未找到指定对象！');

            return false;
        }

        $query->scenario = $scenarios;

        $query->attributes = $arrRequest;

        if ( !$query->validate() ) {
            $this->addErrors($query->errors);
            return false;
        }

        $func = sprintf('%sAsUpdate', $scenarios);

        //根据对应场景选择触发执行方法
        if ( method_exists($this, $func) ) {
            $return = $this->$func($query);
        } else  {
            $func = sprintf('%sAsDefault', $scenarios);
            if ( method_exists($this, $func) ) {
                $return = $this->$func(self::OPERATE_UPDATE, $query);
            } else {
                $return = $this->defaultAsUpdate();
            }
        }

        //如果事件函数返回值为false则 终止余下操作
        if ( $return === false ) {
            return false;
        }

        if ( !is_bool($return) ) {
            throw new \Exception($func.'返回值必须是一个bool');
        }

        $return = $this->updateAll($query->attributes, $arrCondition);

        #获取活动事务
        $transaction = self::getDb()->getTransaction();

        //如果有活动事务
        if ( $transaction && $this->isAutoTransaction ) {
            if ( $return === false ) {
                $transaction->rollBack();
                $this->addError(self::primaryKey()[0], '数据异常');
            } else {
                $transaction->commit();
            }
        }

        return $return;
    }

    /**
     * 新增时默认触发方法
     * @return bool
     */
    public function defaultAsInsert()
    {
    	return true;
    }


    /**
     * 公共添加方法
     * @param array $arrRequest
     * @param string $scenarios
     * @return bool
     * @throws \Exception
     */
    public function addCommon(array $arrRequest, $scenarios="default")
    {
        #设置场景
    	$this->scenario = $scenarios;

    	$this->attributes = $arrRequest;

    	if ( !$this->validate() ) return false;

    	$func = sprintf('%sAsInsert', $scenarios);

    	//根据场景触发调用对应的方法
    	if ( method_exists($this, $func) ) {
    		$return = $this->$func();
    	} else {
            $func = sprintf('%sAsDefault', $scenarios);
            if ( method_exists($this, $func) ) {
                $return = $this->$func(self::OPERATE_INSERT, $this);
            } else {
                $return = $this->defaultAsInsert();
            }
    	}

    	if ( !is_bool($return) ) {
    		throw new \Exception($func.'返回值必须是一个bool');
    	}

        //如果事件函数返回值为false则 终止余下操作
        if ( $return === false ) return false;

    	$return = $this->insert(0);

    	#获取活动事务
        $transaction = self::getDb()->getTransaction();

        //如果有活动事务
    	if ( $transaction && $this->isAutoTransaction ) {
            if ( !$return ) {
                $transaction->rollBack();
                $this->addError(self::primaryKey()[0], '数据异常');
            } else {
                $transaction->commit();
            }
        }

        return $return;
    }

    /**
     * 获取第一个错误
     * 作为字符串
     * @return mixed
     */
    public function getFirstErrorToStr()
    {
        $error = $this->getFirstErrors();

        return  is_array($error) ? reset($error) : null;
    }

    //静态设置错误信息
    public static function error($msg=null, $code=1){
        if($msg===null) {
            $msg = self::$_ERRORS;
            self::$_ERRORS = null;
            return $msg;
        }else{
            self::$_ERRORS = $msg;
            self::$errCode = $code;
        }
    }

    /**
     * 用来获取属性标签
     * @param string $key
     * @return string
     */
    public static function getAttributeLabels($key = 'ALL')
    {
        $static = new static();
        $labels = $static->attributeLabels();
        if ( $key === 'ALL' ) {
            return $labels;
        }
        return isset($labels[$key]) ? $labels[$key] : $key;
    }

    /** 仅推送数据处理 push
     * @param array $data
     * @param $mch_id
     * @param string $prikey
     * @return bool
     * @throws Exception
     */
    public static function push(array $data, $mch_id, $prikey='id'){
        //$transaction = self::getDb()->beginTransaction();
        foreach($data as $v){
            $query = self::findOne([$prikey=>$v[$prikey], 'mch_id'=>$mch_id]);
            //unset($v[$prikey]);
            if ( !$query ) {
                $query = new static();
            }
            $query->attributes = $v;
            if( false===$query->save() ) {
                $query::error($query->getFirstErrorToStr());
                //$transaction->rollBack();
                return false;
            }
        }
        //$transaction->commit();
        return true;
    }
    public static function pushOne2(array $v, $mch_id, $prikey='id'){
        $transaction = self::getDb()->beginTransaction();
        $query = self::findOne([$prikey=>$v[$prikey], 'mch_id'=>$mch_id]);
        //unset($v[$prikey]);
        if ( !$query ) {
            $query = new static();
        }
        $query->attributes = $v;
        if( false===$query->save() ) {
            $query::error($query->getFirstErrorToStr());
            $transaction->rollBack();
            return false;
        }
        $transaction->commit();
        return true;
    }
    public static function pushOne(array $v, $mch_id, $prikey='id'){
        try{
            if(isset($v[$prikey]) && $v[$prikey]!=0){
                $query = self::findOne([$prikey=>$v[$prikey], 'mch_id'=>$mch_id]);
                if ( !$query ) {
                    self::error('数据无效');
                    return false;
                }
                unset($v[$prikey]);
            }else{
                $query = new static();
            }
            $query->attributes = $v;
            if( !$query->save() ) {
                throw new \Exception($query->getFirstErrorToStr());
            }
        }catch (\Exception $e){
            self::error($e->getMessage());
            return false;
        }
        return $query->$prikey;
    }
    /**
     * 仅按主键删除数据处理 del
     *
     * @param int $id
     * @param [type] $mch_id
     * @return void
     */
    public static function delById($id, $mch_id, $prikey='id'){
        try{
            $ret = self::deleteAll([$prikey=>$id, 'mch_id'=>$mch_id]);
        }catch (\Exception $e){
            self::error($e->getMessage());
            return false;
        }
        return $ret;
    }
}
